/**
 * WebSocketProxyServlet.java
 */

package wyse.server.web;

import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocketServlet;

/**
 * Servlet which acts as a proxy for the WYSE server
 * @author cwpl-wkurek
 */
public class WebSocketProxyServlet extends WebSocketServlet {
    /*
     * Ctor
     */
    public WebSocketProxyServlet(int port) {
        mServerPort = port;
    }

    @Override
    protected WebSocket doWebSocketConnect(HttpServletRequest hsr, String string) {
        return new ProxyWebSocket(mServerPort);
    }

    /*
     * The actual proxy websocket handler
     */
    private static class ProxyWebSocket implements WebSocket {
        public ProxyWebSocket(int serverPort) {
            mServerPort = serverPort;
        }

        public void onConnect(Outbound otbnd) {
            mOutbound = otbnd;
            try {
                mSocket = new Socket(InetAddress.getByName("localhost"), mServerPort);

                asyncReceiveMessage();
            } catch (IOException ex) {
                Logger.getLogger(WebSocketProxyServlet.class.getName()).log(Level.SEVERE, "Failed to connect to the WYSE server");
                mOutbound.disconnect();
                mOutbound = null;
            }
        }

        /*
         * receives a message asynchronously
         */
        private void asyncReceiveMessage() {
            ExecutorService exec = getExecutor();

            exec.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        synchronized(ProxyWebSocket.this)
                        {
                            InputStream s = mSocket.getInputStream();
                            byte[] header = new byte[8];

                            if(s.read(header) != 8)
                                return;

                            // extract the size from the header
                            String headerStr = new String(header, "UTF-8");
                            int size = Integer.parseInt(headerStr.substring(4), 16);

                            byte[] data = new byte[size];
                            if(s.read(data) != data.length)
                                return;

                            forwardMessage(data);
                        }

                        asyncReceiveMessage();
                    } catch (IOException ex) {
                    }
                }
            });
        }

        private void forwardMessage(byte[] data) throws IOException {
            String msg = new String(data, "UTF-8");

            mOutbound.sendMessage(msg);
        }

        /*
         * Forwards the message to the real server
         */
        public void onMessage(byte b, String string) {
            try {
                byte[] messageBytes;

                if(!string.startsWith("WY"))
                {
                    // the message does not contain a header, add it

                    byte[] dataBytes = string.getBytes("UTF-8");

                    StringBuilder sb = new StringBuilder();
                    sb.append("WY01");
                    sb.append(String.format("%04X", dataBytes.length));

                    byte[] headerBytes = sb.toString().getBytes("UTF-8");

                    messageBytes = new byte[headerBytes.length + dataBytes.length];
                    System.arraycopy(headerBytes, 0, messageBytes, 0, 8);
                    System.arraycopy(dataBytes, 0, messageBytes, 8, dataBytes.length);
                } else {
                    messageBytes = string.getBytes("UTF-8");
                }

                mSocket.getOutputStream().write(messageBytes);
            } catch(IOException ie) {
                mOutbound.disconnect();
                mOutbound = null;
            } catch(Exception e) {
                
            }
        }

        public void onFragment(boolean bln, byte b, byte[] bytes, int i, int i1) {
        }

        public void onMessage(byte b, byte[] bytes, int i, int i1) {
        }

        public void onDisconnect() {
            if(mSocket != null) {
                try {
                    mSocket.close();
                    mSocket = null;
                } catch (IOException ex) {
                }
            }
        }

        private Socket mSocket;
        private Outbound mOutbound;
        private int mServerPort;
    }

    private static ExecutorService getExecutor() {
        if(sExecutor == null) {
            sExecutor = Executors.newCachedThreadPool();
        }

        return sExecutor;
    }

    private static ExecutorService sExecutor;
    private int mServerPort;
}
